#include <stdlib.h>
#include "Scheduler.h"
#include <iostream>
// TODO : remove
#include <stdio.h>
#include <unistd.h>
// for beep():
#include <stdlib.h>
#include <fcntl.h>
#include <linux/kd.h>


Scheduler::Scheduler(vector<Worker*>* workers, Mailbox* shell_inbox) :
			_workers(workers),
			_shell_inbox(shell_inbox),
			//_running_thread(0),
			_next_thread_to_run(0),
			_next_quantum(0),
			_scheduling_algorithm(0),
			_round_robin_index(1),
			_is_state_stabilized(false),
			_got_revive_message(false)
{
	_threads_table = new Thread_Table(_workers->size()-1);

	init_statistics_counters();

	// start all workers in "ready" state:


	//for (unsigned int i = 1; i < _workers->size(); ++i) {
	//	Worker* worker = _workers->at(i);
	//	// Elad : add first update to threads table : all worker are ready
	//	_threads_table->get_worker_entry(i)->setStatus(THREAD_READY);
	//	worker->start();
	//	cout << "Worker " << i << " started" << endl << endl;
	//}
}

Scheduler::~Scheduler() {
	delete(_threads_table);
}

void Scheduler::do_context_switch(int regular_or_extra)
{
	// handle last worker thread:
	m_curThread->setActive(false);
	//m_curThread->setQuanta(NO_QUANTA);			// shai     we must NOT do that because it causes the worker to wake up from its yield(), it changes the field 'yielded' to be true




	if (_round_robin_index == -1) init_RR_index();

	get_next_unblocked_worker();
	if ( _round_robin_index == -1 )
	{
		cout << "all workers are blocked" << endl;
	}
	else
	{
		_the_number_of_context_switches_between_worker_threads++;
		for (unsigned int i = 1; i < _workers->size(); ++i) {
			int a = ((Worker*)_workers->at(i))->getTotal_number_of_messages_delivered_to_the_thread_from_the_last_quantum();
			_the_actual_number_of_messages_passed_from_last_initialization += a;
		}


		// handle current worker thread:
		cout << "RR: Next thread to run is " << _round_robin_index << endl;
		_threads_table->get_worker_entry(_round_robin_index)->setStatus(THREAD_RUNNING);
		m_curThread->setStatus(THREAD_RUNNING);
		m_curThread->setActive(true);
		//cout << "____________________ Worker ____________________" << endl;

		// used for EXTRA_RR only:
		bool double_quanta = m_curThread->getWas_blocked_in_last_run();
		m_curThread->setWas_blocked_in_last_run(false);
		int quanta_to_give = compute_quanta_to_give(regular_or_extra, double_quanta);


		EntryTT* entry_current_thread = _threads_table->get_worker_entry(_round_robin_index);
		entry_current_thread->setAcumulated_message_quanta(entry_current_thread->getAcumulated_message_quanta() + quanta_to_give);
		entry_current_thread->setTotal_number_delivered_from_last_quantum(m_curThread->getTotal_number_of_messages_delivered_to_the_thread_from_the_last_quantum());
		entry_current_thread->setPriority(1);


		m_curThread->setTotal_number_of_messages_delivered_to_the_thread_from_the_last_quantum(0);


		m_curThread->setQuanta(quanta_to_give);
		int t=0;
		while ( t < 90000)	t++;
	}
}

int Scheduler::compute_quanta_to_give(int flag, bool double_quanta)
{
	if ( flag == REGULAR )
	{
		return ROUND_ROBIN_QUANTA;
	}
	else	// flag == EXTRA
	{
		return ROUND_ROBIN_QUANTA * ( double_quanta ? 2 : 1 );
	}
}


void Scheduler::schedule(int mode)
{
	switch (_scheduling_algorithm)
	{
		case ROUND_ROBIN:
			round_robin(mode, REGULAR);
			break;

		case MULTI_LEVEL:
			multi_level(mode);
			break;

		case THE_BEST_ALGO_IN_TOWN:
			the_best_algo(mode);
			break;

		default:
			cout << "No such algorithm" << endl;
			break;
	}
}

bool Scheduler::is_next_worker_blocked_or_killed()
{
	bool ans_blocked = _threads_table->get_worker_entry(_round_robin_index)->getStatus() == THREAD_BLOCKED;
	if (ans_blocked)
	{
		cout << "Scheduler: Worker " << _round_robin_index << " is blocked" << endl;
	}
	bool ans_killed = ((Worker*)_workers->at(_round_robin_index))->getKilled();
	if (ans_killed)
	{
		cout << "Scheduler: Worker " << _round_robin_index << " is killed" << endl;
	}
	return ans_blocked  ||  ans_killed;
}

// for round robin only:
void Scheduler::advance_to_next_worker_RR()
{
    _round_robin_index++;
    if(_round_robin_index == (int)((_workers->size()))){
        _round_robin_index = 1;
    }
}

// for round robin only:
void Scheduler::get_next_unblocked_worker()
{
	//TODO : remove sleep .. just for now, until sig fault is fix
	//sleep(1);


	advance_to_next_worker_RR();
	int workers_blocked = 0;
	while ( workers_blocked < (int)(_workers->size()-1)  &&  is_next_worker_blocked_or_killed() )
	{

		workers_blocked++;
		 advance_to_next_worker_RR();
	}

	if ( workers_blocked == (int)_workers->size()-1 )
	{
		_round_robin_index = -1;
	}
	else
	{
//		if(_round_robin_index == (int)(_workers->size()))
//		{
//			// Complete round
//			cout << "complete full round" << endl;
//			_round_robin_index = 1;
//		}

		// Elad : added check if next thread isn't block, if block advance one more time.

		_next_thread_to_run = _workers->at(_round_robin_index);
		notifyThreadChange(_next_thread_to_run);
	}
}

void Scheduler::set_all_workers_alive(bool alive_mode)
{
    for(unsigned int i = 1;i < _workers->size();++i){
        ((Worker*)(_workers->at(i)))->setIs_alive(alive_mode);
    }
}

void Scheduler::beep()
{
	//int fd = open("/dev/tty10", O_RDONLY);
}

bool Scheduler::is_state_stabilized()
{
	bool ans, ans_quanta = true, ans_blocked = true;

	// ans_quanta:
	for (unsigned int i = 1; i < _workers->size() && ans_quanta; ++i) {
		ans_quanta = ans_quanta && ((Worker*)_workers->at(i))->get_Yielded_with_given_quanta() == ROUND_ROBIN_QUANTA;
	}

	// ans_blocked:
	for (unsigned int i = 1; i < _workers->size() && ans_blocked; ++i) {
		ans_blocked = ans_blocked && ((Worker*)_workers->at(i))->getStatus() == THREAD_BLOCKED;
	}

	ans = ans_quanta  ||  ans_blocked  ||  (_round_robin_index == -1  &&  !_got_revive_message);
	if ( ans )
	{
		cout << "State stabilized. reason: ";
		if (ans_quanta)
		{
			cout << "All workers yielded with given quanta. ";
		}
		if (ans_blocked)
		{
			cout << "All workers are BLOCKED. ";
		}
		if (_round_robin_index == -1)
		{
			cout << "_round_robin_index == -1  &&  !_got_revive_message. ";
		}
		cout << endl;
	}
	return ans;
}

void Scheduler::init_RR_index()
{
    _round_robin_index = _workers->size() - 1;
}

void Scheduler::round_robin(int mode, int regular_or_extra)
{
	if ( mode == INIT )
	{
		for (unsigned int i = 1; i < _workers->size(); ++i) {
			Worker* worker = _workers->at(i);
			_threads_table->get_worker_entry(i)->setStatus(THREAD_READY);
			worker->start();
			cout << "Worker " << i << " started" << endl << endl;
		}








		cout << endl << "round_robin: INIT" << endl;
	    set_all_workers_alive(true);

	    m_curThread = _workers->at(1);
	    m_curThread->setQuanta(ROUND_ROBIN_QUANTA);
	    m_curThread->setActive(true);

	    EntryTT* entry = _threads_table->get_worker_entry(_round_robin_index);
	    entry->setStatus(THREAD_RUNNING);
	    entry->setTotal_number_delivered_from_last_quantum(0);
	    entry->setPriority(0);
	    entry->setAcumulated_message_quanta(ROUND_ROBIN_QUANTA);

	    cout << "round_robin: INIT finished" << endl;
	    return;
	}
	else if ( mode == SCHEDULE )
	{
		cout << "round_robin: SCHEDULE" << endl;
		if ( is_state_stabilized() )
		{
		    _is_state_stabilized = true;
		    _threads_table->block_all();
		    init_RR_index();
		    Message_System* m = new Message_System(MAILER, MAILER, SHELL, SHELL, "State Stabilized");

		    // summary prints:
		    for (unsigned int i = 1; i < _workers->size(); ++i) {
		    	_sum_of_message_quanta_given_to_all_worker_threads_from_last_initialization += _threads_table->get_worker_entry(i)->getAcumulated_message_quanta();
			}

		    cout << endl;
		    cout << "===============================================================================" << endl << "                                    Statistics" << endl << "===============================================================================" << endl;
		    cout << "The number of context switches between worker threads                     : ";
		    cout << _the_number_of_context_switches_between_worker_threads << endl;
		    cout << "Sum of message quanta given to all worker threads from last initialization: ";
		    cout << _sum_of_message_quanta_given_to_all_worker_threads_from_last_initialization << endl;
		    cout << "The actual number of messages passed from last initialization             : ";
		    cout << _the_actual_number_of_messages_passed_from_last_initialization << endl;
		    _threads_table->print();
		    cout << endl;
		    cout << "===============================================================================" << endl;

		    // initializes:
		    init_statistics_counters();
		    _threads_table->re_init_TT();


		    // sending shell an updating message:
		    _shell_inbox->put_message(m);
		}
		else
		{
			cout << endl << "before TT updating:" << endl;
			_threads_table->print();

			// for each worker:
			// 1. if getphase is odd, then:
			//			if _status == RUNNING, then set status to READY		(means: stuck on quanta in recv()/send())
			//			if _status == BLOCKED, then set status to BLOCKED	(means: stuck on _inbox_empty in studentRecv() OR on mailer_inbox_full in studentSend())
			for (unsigned int i = 1; i < _workers->size(); ++i) {
				Worker* w = (Worker*)_workers->at(i);

				if ( w->getPhase() % 2 == 1 )
				{
					int real_status = -1;
					switch ( w->getStatus() )
					{
						case THREAD_RUNNING:	real_status = THREAD_READY;
												break;
						case THREAD_BLOCKED:	real_status = THREAD_BLOCKED;
												//w->setQuanta(NO_QUANTA);
												break;
						case THREAD_READY:		real_status = THREAD_READY;
												//w->setQuanta(NO_QUANTA);
												break;
						default:				//cout << "Not supposed to be here" << endl;
												break;
					}
					w->setStatus(real_status);
					_threads_table->get_worker_entry(i)->setStatus(real_status);
					_threads_table->get_worker_entry(i)->setCurrent_quanta(w->getQuanta());
				}
				else		// Modulo 2 == 0
				{
					int real_status 				= -1;
					bool apperently_stuck_on_quanta = false;
					switch ( w->getStatus() )
					{
						case THREAD_RUNNING:	//cout << "NOT LIKELY TO BE RUNNING HERE" << endl;
												while ( w->getStatus() == THREAD_RUNNING )
												{
													//cout << i << " still running..." << endl;
													if ( w->getPhase() % 2 == 1 )
													{
														apperently_stuck_on_quanta = true;
														break;
													}
													sleep(1);
												}
												if ( w->getPhase() % 2 == 1 )
												{
													if ( apperently_stuck_on_quanta )
													{
														real_status = THREAD_READY;
													}
													else
													{
														real_status = THREAD_BLOCKED;
													}
												}
												else
												{
													//cout << "Impossible" << endl;
												}
												break;

						case THREAD_BLOCKED:	real_status = THREAD_BLOCKED;
												break;

						case THREAD_READY:		real_status = THREAD_READY;
												//cout << "READY HERE" << endl;
												break;

						default:				//cout << "Not supposed to be here" << endl;
												break;
					}
					//w->setStatus(real_status);
					_threads_table->get_worker_entry(i)->setStatus(real_status);
				}

				// used for EXTRA RR only:
				if (_threads_table->get_worker_entry(_round_robin_index)->getStatus() == THREAD_BLOCKED )
				{
					m_curThread->setWas_blocked_in_last_run(true);
				}
			}
			cout << endl << "after TT updating:" << endl;
			_threads_table->print();
			do_context_switch(regular_or_extra);
		}

		if ( _got_revive_message )
		{
			_got_revive_message = false;
		}
	}
}
void Scheduler::multi_level(int mode)
{
	cout << "multi_level" << endl;
}

void Scheduler::the_best_algo(int mode)
{
	round_robin(mode, EXTRA);
}

void Scheduler::init_statistics_counters()
{
	_the_number_of_context_switches_between_worker_threads						= 0;
	_sum_of_message_quanta_given_to_all_worker_threads_from_last_initialization = 0;
	_the_actual_number_of_messages_passed_from_last_initialization				= 0;
}
